import processing.core.*; 
import processing.xml.*; 

import java.applet.*; 
import java.awt.Dimension; 
import java.awt.Frame; 
import java.awt.event.MouseEvent; 
import java.awt.event.KeyEvent; 
import java.awt.event.FocusEvent; 
import java.awt.Image; 
import java.io.*; 
import java.net.*; 
import java.text.*; 
import java.util.*; 
import java.util.zip.*; 
import java.util.regex.*; 

public class spaceminers_sketch extends PApplet {

boolean isMouseDown = false;
tShapeContainer globalShapeContainer = null;
int screenSize[] = {1294,800};
tClueOverlay globalClueOverlay = null;
boolean clueIsActive = false;
tPalette globalPalette = null;
tButton globalSubmitButton = null;
int frameOnWhichMouseWasLastPressed = -1;
int globalGridSize = 1;
PFont menuFont = null;

images img;
menu mainMenu;

//0 : into; 1 : menu; 2 : game
int gameState = 2;
//0 : easy; 1 : medium; 2 : hard
int difficulty = 0;

public void setup()
{
  img = new images();
  mainMenu = new menu();
  size(screenSize[0],screenSize[1],P3D);
  background(0);
  globalShapeContainer = new tShapeContainer();
  globalShapeContainer.initStandardShapes();
  globalClueOverlay = new tClueOverlay();
  globalPalette = new tPalette();
  menuFont = loadFont("data/SakkalMajallaBold-30.vlw");
  globalSubmitButton = createSubmitButton();
  elementsSetup();
  clueSetup();
}

public void draw()
{
  update();
  background(0);
  
  switch(gameState)
  {
    case 0:
      img.drawIntro();
      break;
    case 1:
      mainMenu.drawMenu();
      break;
    case 2:
      drawGame();
      break;
  }
}

public void drawGame()
{
  globalPalette.doDraw();
  globalShapeContainer.doDraw();
  globalSubmitButton.doDraw();
  globalClueOverlay.doDraw();
//  println(globalShapeContainer.getElementIDs(false));
}
public void update()
{
  int[] mousePos = getMousePos();
  println(globalPalette.getCollisionName(mousePos));
  globalPalette.update(mousePos);
  globalShapeContainer.update(mousePos);
  globalSubmitButton.update(mousePos);
  globalClueOverlay.update(mousePos);
}

public int[] getMousePos()
{
  int mousePos[] = {
    (int)mouseX, (int)mouseY
  };
  return mousePos;
}

public void mousePressed()
{
  isMouseDown = true;
  frameOnWhichMouseWasLastPressed = frameCount;
}

public void mouseReleased()
{
  isMouseDown = false;
}

public boolean getMouseDownLeft()
{
  if (mouseButton == LEFT && isMouseDown && !clueIsActive)
  {
    return true;
  }
  else
  {
    return false;
  }
}
class tButton extends tShape
{
  String myName;
  boolean pressed;
  String onClickFunction;
  
  tButton(int[][] newCoords, int newColor, String newName, String newOnClickFunction)
  {
    super(newCoords, newColor);
    myName = newName;
    pressed = false;
    onClickFunction = newOnClickFunction;
  }
  
  public void update(int[] mousePos)
  {
    super.update(mousePos, false);
    if (getMouseDownLeft())
    {
      if (pressed == false && hovered)
      {
        pressed = true;
      }
    }
    else
    {
      if (pressed && hovered)
      {
        this.onClick();
      }
      pressed = false;
    }
  }
  
  public void onClick()
  {
    if (onClickFunction == "submit")
    {
      submit();
    }
  }
  
  public boolean doDraw()
  {
    if (!super.doDraw())
    {
      return false;
    }
    int w = coords[1][0] - coords[0][0];
    int h = coords[2][1] - coords[0][1];
    int padding = (h - 16)/2;
    writeLargeText(myName, coords[0][0] + padding, coords[0][1] + padding);
    return true;
  }
  
  public void writeLargeText(String toWrite, int x, int y)
  {
    PFont font = menuFont;
    textFont(font);
    text(toWrite, x, y);
  }
}

public tButton createSubmitButton()
{
  int halfw = screenSize[0]/2;
  int w = 200;
  int h = 50;
  int halfh = screenSize[0]-50;
  int coords[][] = { {halfw-w/2,halfh-h/2}, {halfw+w/2,halfh-h/2}, {halfw+w/2,halfh+h/2}, {halfw-w/2,halfh+h/2} };
  return new tButton(coords, color(100,100,100,200), "Submit", "submit");
}

public void submit()
{
}
/**
 * Displays the clue overlay
 * Will set the "ignoreMouseBecauseOfClueOverlay" variable
 */
class tClueOverlay
{
  tShape cornerShape;
  tShape fullShape;
  // <0.05 is in the corner, >0.05 is the full screen
  float state;
  tClue clues[];
  
  tClueOverlay()
  {
    int diff = min(screenSize[0]/12, screenSize[1]/6);
    int[][] cornerCoords = {{0,0}, {diff,0}, {0,diff}};
    cornerShape = new tShape(cornerCoords, color(180,180,220,125));
    int[][] fullCoords = {{diff,0}, {screenSize[0]-diff,0}, {screenSize[0],diff}, {screenSize[0],screenSize[1]-diff},
                          {screenSize[0]-diff,screenSize[1]}, {diff,screenSize[1]}, {0,screenSize[1]-diff}, {0,diff}};
    fullShape = new tShape(fullCoords, color(180,180,220,125));
    state = 0;
  }
  
  /**
   * generates a new clue based on elementIDs
   * @param elementIDs should be a colon-seperated-values list of element ids
   *  eg 0:1:2:etc
   * @param addClue can be "assign" or "append"
   */
  public void generateNewClues(String elementIDs, String addClue)
  {
    println("a");
    // get a new clue
    if (addClue == "assign")
    {
      println("a");
      tClue newClues[] = {new tClue()};
      clues = newClues;
    }
    else if (addClue == "append")
    {
      println("a");
      tClue newClues[] = new tClue[clues.length+1];
      for (int i = 0; i < clues.length; i++)
      {
        newClues[i] = clues[i];
      }
      newClues[newClues.length-1] = new tClue();
      clues = newClues;
    }
    else
    {
      return;
    }
    
    // add the lines to the clue
    println("b");
    clues[clues.length-1].addLine(adjPull(elementIDs));
    
    // test
    println("b");
    clues[clues.length-1].reset();
    String ln = clues[clues.length-1].getNextLine();
    while (ln != null);
    {
      println("c");
      println(ln);
      ln = clues[clues.length-1].getNextLine();
    }
  }
  
  // all of this updates the position of the fullShape
  // and the alphas of the cornerShape and fullShape
  public void update(int[] mousePos)
  {
    if (cornerShape.getPointCollision(mousePos) && state < 0.05f && frameCount > 10)
    {
      clueIsActive = true;
      generateNewClues("0:1:2:3:4:5:6:7:8:9", "assign");
    }
    
    if (state > 0.95f && mouseButton == LEFT && isMouseDown)
    {
      clueIsActive = false;
      isMouseDown = false;
    }
    
    if (clueIsActive)
    {
      state += 0.05f;
      state = min(state, 1.0f);
    }
    else
    {
      state -= 0.05f;
      state = max(state, 0);
    }
     
    if (state > 0.05f && state < 0.95f)
    {
      cornerShape.setAlpha((int)(255.0f * (1.0f - state)));
      fullShape.setAlpha((int)(255.0f * state));
      int newPosition[] = { -(int)((1-state)*screenSize[0]), -(int)((1-state)*screenSize[1]) };
      fullShape.setPosition(newPosition);
    }
    else
    {
      if (state <= 0.05f)
      {
        int newPosition[] = { -screenSize[0], -screenSize[1] };
        fullShape.setPosition(newPosition);
      }
      else
      {
        int newPosition[] = { 0, 0 };
        fullShape.setPosition(newPosition);
      }
    }
  }
  
  public void doDraw()
  {
    cornerShape.doDraw();
    fullShape.doDraw();
  }
}

/**
 * Contains the text for the haikus
 */
class tClue
{
  String myLines[];
  int currentLine;
  
  public void addLine(String newLine)
  {
    if (myLines != null && myLines.length > 0)
    {
      String oldLines[] = myLines;
      myLines = new String[myLines.length];
      for (int i = 0; i < oldLines.length; i++)
      {
        myLines[i+1] = oldLines[i];
      }
    }
    else
    {
      myLines = new String[1];
    }
    myLines[0] = newLine;
  }
  
  /**
   * The next time getNextLine is called, 
   * the first line will be returned
   * @see getNextLine()
   */
  public void reset()
  {
    currentLine = 0;
  }
  
  /**
   * Returns the next line in the clue
   * @return tne next line in the clue, or null if there are no more lines
   * @see reset()
   */
  public String getNextLine()
  {
    if (currentLine > myLines.length-1)
    {
      return null;
    }
    
    currentLine++;
    return myLines[currentLine-1];
  }
}
String[] adj;

public void clueSetup() {
  adj = loadStrings("adj.txt");
}

public String adjPull(String elements)
{
  String[] formula = split(elements, ":");
  String[] intermed;
  int[] elementIDs = PApplet.parseInt(formula);
  char search1 = ' ';
  char search2 = ' ';
  String full = "";
  
  for(int i = 0; i < elementIDs.length; i++)
  {
    String mid = "";
      
    switch(elementIDs[i]) {
      case 0: search1 = 'c';
        search2 = 'a';
        break;
      case 1: search1 = 'c';
        search2 = 'a';
        break;
      case 2: search1 = 'f';
        search2 = 'i';
        break;
      case 3: search1 = 'h';
        search2 = 'e';
        break;
      case 4: search1 = 'h';
        search2 = 'y';
        break;
      case 5: search1 = 'i';
        search2 = 'c';
        break;
      case 6: search1 = 's';
        search2 = 'i';
        break;
      case 7: search1 = 's';
        search2 = 'o';
        break;
      case 8: search1 = 's';
        search2 = 'u';
        break;
      case 9: search1 = 't';
        search2 = 'i';
        break;
    }
    
      for(int j = 0; j < adj.length;j++)
      { 
        if(adj[j].charAt(0) == search1 && adj[j].charAt(1) == search2)
        {
          mid = mid + adj[j] + ",";
        }
      }
      
      intermed = split(mid, ",");
      int pick = (int)random(0,intermed.length);
      full = full + intermed[pick] + " ";
  }
  
  return full;
}


public boolean checkCollisionWithEdges(int[][] _coordsA, int[][] _coordsB, int[] positionA, int[] positionB)
{
  return _checkCollision(_coordsA, _coordsB, positionA, positionB, true);
}

public boolean checkCollision(int[][] _coordsA, int[][] _coordsB, int[] positionA, int[] positionB)
{
  return _checkCollision(_coordsA, _coordsB, positionA, positionB, false);
}

/**
 * Checks for the existence of the coordinates of convex polygon B inside the coordinates of convex polygon A
 * Positions are the screen positions of the first coordinate in the list of coordinates
 */
public boolean _checkCollision(int[][] _coordsA, int[][] _coordsB, int[] positionA, int[] positionB, boolean detectEdges)
{
  int[][] coordsA = new int[_coordsA.length][2];
  int[][] coordsB = new int[_coordsB.length][2];
  
  // get the absolute coordinates
  for (int i = 0; i < coordsA.length; i++)
  {
    coordsA[i][0] = _coordsA[i][0] + positionA[0];
    coordsA[i][1] = _coordsA[i][1] + positionA[1];
  }
  for (int i = 0; i < coordsB.length; i++)
  {
    coordsB[i][0] = _coordsB[i][0] + positionB[0];
    coordsB[i][1] = _coordsB[i][1] + positionB[1];
  }
  
  // check for special cases
  if (coordsB.length == 1)
  {
    // both arrays are length 1
    if (coordsA.length == 1)
    {
      if (coordsA[0][0] == coordsB[0][0] && coordsA[0][1] == coordsB[0][1])
      {
        return true;
      }
      return false;
    }
    // array A should have 1 point and array B should be the shape
    int[][] tempCoords = coordsA;
    coordsA = coordsB;
    coordsB = tempCoords;
  }
  
  // check for a collision
  for (int i = 0; i < coordsA.length; i++)
  {
    if (checkPointCollision(coordsB, coordsA[i], detectEdges))
    {
      return true;
    }
  }
  
  return false;
}

/**
 * check that a point is inside a set of coordinates
 * Coordinate order matters! It must proceed clockwise!
 * @param inCoords the coordinates of the convex polygon
 * @param pos two values denoting the location of the point
 */
public boolean checkPointCollision(int[][] inCoords, int[] pos, boolean detectEdges)
{
  for (int i = 0; i < inCoords.length; i++)
  {
    int[][] coords = new int[2][2];
    coords[0] = inCoords[i];
    int j = i+1;
    while (j > inCoords.length - 1)
    {
      j -= inCoords.length;
    }
    coords[1] = inCoords[j];
    
    if (!checkPointToRightOfLine(coords, pos, detectEdges))
    {
      return false;
    }
  }
  
  return true;
}

/**
 * check that a point is to the "right" of a line
 * line coordinate order matters!
 * @param lineCoords a set of two coordinates for the start and end of a line
 * @param pos two values denoting the location of the point
 */
public boolean checkPointToRightOfLine(int[][] lineCoords, int[] pos, boolean detectEdges)
{
  boolean lineIsVertical = false;
  boolean lineGoesRight = false;
  float slope = 0;
  int yInterceptLine = 0;
  int yInterceptPoint = 0;
  
  // check for vertical lines
  if (lineCoords[0][0] == lineCoords[1][0])
  {
    lineIsVertical = true;
    if (!detectEdges)
    {
      if (pos[0] < lineCoords[0][0] && lineCoords[0][1] < lineCoords[1][1])
      {
        return true;
      }
      if (pos[0] > lineCoords[0][0] && lineCoords[0][1] > lineCoords[1][1])
      {
        return true;
      }
    }
    else
    {
      if (pos[0] <= lineCoords[0][0] && lineCoords[0][1] < lineCoords[1][1])
      {
        return true;
      }
      if (pos[0] >= lineCoords[0][0] && lineCoords[0][1] > lineCoords[1][1])
      {
        return true;
      }
    }
    return false;
  }
  
  // find the slope
  if (lineCoords[1][1] == lineCoords[0][1])
  {
    slope = 0;
  }
  else
  {
    slope = ((float)lineCoords[1][1] - (float)lineCoords[0][1]) / ((float)lineCoords[1][0] - (float)lineCoords[0][0]);
  }
  
  // find the yIntercepts
  yInterceptLine = lineCoords[0][1] - (int)(slope*(float)lineCoords[0][0]);
  yInterceptPoint = pos[1] - (int)(slope*(float)pos[0]);
  
  // determine if the line goes left or right
  lineGoesRight = true;
  if (lineCoords[0][0] > lineCoords[1][0])
  {
    lineGoesRight = false;
  }
  
  // right low, left high
  if (lineGoesRight)
  {
    if ((yInterceptPoint > yInterceptLine && !detectEdges) ||
        (yInterceptPoint >= yInterceptLine && detectEdges))
    {
      return true;
    }
  }
  else
  {
    if ((yInterceptPoint < yInterceptLine && !detectEdges) ||
        (yInterceptPoint <= yInterceptLine && detectEdges))
    {
      return true;
    }
  }
  
  return false;
}
float[][] Elements;
SystemType[] SystemTypes; //contains the system type data
StarType[] StarTypes; //contains the star type data
PlanetType[] PlanetTypes;
SolarSystem mySystem;
int NUM = 100;
PVector[] exppos;
PVector[] expvel;
PFont fontA;

public void elementsSetup() {
  
  exppos = new PVector[NUM];
    expvel = new PVector[NUM];
    for( int i = 0; i < NUM; i++ ) {
      exppos[i] = new PVector(0,-height/2);
      expvel[i] = new PVector(random(-10,10), random (-15,15));
    }
    
  fontA = loadFont("SakkalMajallaBold-30.vlw");

  // Set the font and its size (in units of pixels)
  textFont(fontA, 32);
  
  Elements = new float[10][5];
  float[] Elements0 = {1.4f*sunRadius, 0.8f*sunRadius, 237, 238, 189};
  Elements[0] = Elements0;
  float[] Elements1 = {1.5f*earthRadius, 0.5f*earthRadius, 183, 251, 150};
  Elements[1] = Elements1;
  float[] Elements2 = {15*sunRadius, 2.5f*sunRadius, 206, 244, 8};
  Elements[2] = Elements2;
  float[] Elements3 = {15*earthRadius, 7*earthRadius, 219, 193, 155};
  Elements[3] = Elements3;
  float[] Elements4 = {1.6f*sunRadius, 1.0f*sunRadius, 231, 235, 235};
  Elements[4] = Elements4;
  float[] Elements5 = {12*earthRadius, 6*earthRadius, 218, 207, 232};
  Elements[5] = Elements5;
  float[] Elements6 = {0.4f*earthRadius, 0.2f*earthRadius, 197, 196, 198};
  Elements[6] = Elements6;
  float[] Elements7 = {1.2f*sunRadius, 0.6f*sunRadius, 250, 176, 71};
  Elements[7] = Elements7;
  float[] Elements8 = {0.9f*earthRadius, 0.5f*earthRadius, 250, 227, 71};
  Elements[8] = Elements8;
  float[] Elements9 = {0.5f*sunRadius, 0.3f*sunRadius, 208, 12, 7};
  Elements[9] = Elements9;

  String[] starrows = loadStrings("suntypes.txt");
  String[] planetrows = loadStrings("planettypes.txt");
  String[] systemrows = loadStrings("systemtypes.txt");

  SystemTypes = new SystemType[systemrows.length]; //contains the system type data
  StarTypes = new StarType[starrows.length]; //contains the star type data
  PlanetTypes = new PlanetType[planetrows.length]; //contains the planet type data

  //get the star types
  for (int i = 0; i < starrows.length-2;i++)
  {
    String[] myStar1 = split(starrows[i+2], ',');
    String[] myStar2 = new String[myStar1.length-1];
    float[] starData = new float[myStar1.length-1];
    for (int j = 0; j < myStar1.length-1;j++)
    {
      myStar2[j] = myStar1[j];
    }
    starData = PApplet.parseFloat(myStar2);
    StarTypes[i] = new StarType(starData, myStar1[myStar1.length-1]);
  }

  //get the planet types
  for (int i = 0; i < planetrows.length-2;i++)
  {
    String[] myPlanet1 = split(planetrows[i+2], ",");
    float[] planetData = new float[myPlanet1.length];
    planetData = PApplet.parseFloat(myPlanet1);
    PlanetTypes[i] = new PlanetType(planetData);
  }

  //get the system types
  for (int i = 0; i < systemrows.length-2;i++)
  {
    String[] mySystem1 = split(systemrows[i+2], ",");
    String[] mySystem2 = new String[mySystem1.length-1];
    int[] systemData = new int[mySystem1.length-1];
    for (int j = 0; j < mySystem1.length-1;j++)
    {
      mySystem2[j] = mySystem1[j];
    }
    systemData = PApplet.parseInt(mySystem2);
    SystemTypes[i] = new SystemType(systemData, mySystem1[mySystem1.length-1]);
  }
  
  mySystem = new SolarSystem(0);
}

/*void draw()
{
  //mySystem.displaySystem();
}*/
//sandbox size : 970x600
public void ripSandbox()
{
  PImage sandbox = get(162,100,970,600);
  sandbox.save("saved_images/ripped.jpg");
}

class images
{
  PImage splash;
  PImage story;
  PImage menuBackground;
  PImage credits;
  PImage easybg;
  PImage medbg;
  PImage hardbg;
  PImage palette;
  
 
  images()
  {
    background(100,100,100);
    splash = loadImage("images/titlescreen.jpg");
    story = loadImage("images/story.jpg");
    menuBackground = loadImage("images/trigalaxy.jpg");
    credits = loadImage("images/credits.jpg");
    easybg = loadImage("images/easybg.jpg");
    medbg = loadImage("images/medbg.jpg");
    hardbg = loadImage("images/hardbg.jpg");
    palette = loadImage("images/palette.jpg");
  }
  
  int panel = 1;
  boolean mouseDown = false;
  public void drawIntro()
  {
    if(panel > 2)
    {
     gameState = 1; 
     return;
    }
    if(isMouseDown && !mouseDown)
    {
      mouseDown = true;
    }
    if(mouseDown && !isMouseDown)
    {
     mouseDown = false;
     panel++;
    }
    this.drawPanel(panel);
  }
  public void drawPanel(int number)
  {
    switch(number) 
    {
      case 1:
        image(splash,0,0);
        break;
      case 2:
        image(story,0,0);
        break;
      default:
        background(0);
    }
  }
}

class menu
{
 int[][] galaxyCoords = {
                         {406,400},
                         {753,346},
                         {1097,261}
                        };
 boolean mouseDown = false;
 boolean mouseClick = false;
 
 public void drawMenu()
 {
   image(img.menuBackground, 0, 0);
   if(getDistToMouse(galaxyCoords[0]) < 100)
   {
     writeLargeText("Easy", 400,200);
     if(mouseClick)
     {
       difficulty = 0;
       gameState = 2;
       return;
     }
   }
     
   if(getDistToMouse(galaxyCoords[1]) < 100)
   {
     writeLargeText("Medium", 720, 150);
     if(mouseClick)
     {
       difficulty = 0;
       gameState = 2;
       return;
     }
   }
     
   if(getDistToMouse(galaxyCoords[2]) < 100)
   {  
     writeLargeText("Hard",1100,75);
     if(mouseClick)
     {
       difficulty = 0;
       gameState = 2;
       return;
     }
   }
   
   //--------search for mouse clicks
   mouseClick = false;
   if(isMouseDown && !mouseDown)
    {
      mouseDown = true;
    }
    if(mouseDown && !isMouseDown)
    {
     mouseDown = false;
     mouseClick = true;
    }
 }
 
 public void writeLargeText(String toWrite, int x, int y)
 {
   PFont font = menuFont;
   textFont(font);
   text(toWrite, x, y);
 }
 
 public float getDistToMouse(int[] coords)
 {
   return sqrt(pow((coords[0] - mouseX),2) + pow((coords[1] - mouseY),2));
 }
}


class tPalette
{
  tPaletteFlavor flavors[];
  // in the form [x,y,w,h]
  int dims[];
  // <0.05 is hidden, >0.05 is floating on the top of the screen
  float state;
  int recessedY;
  int extendedY;
  boolean active;
  
  tPalette()
  {
    int halfx = screenSize[0] / 2;
    int imageWidthHalf = 1000 / 2;
    int imageHeight = 100;
    recessedY = -imageHeight+25;
    extendedY = 0;
    int newDims[] = {halfx-imageWidthHalf, recessedY, imageWidthHalf*2, imageHeight};
    dims = newDims;
    active = false;
    state = 0;
    int numFlavors = 10;
    flavors = new tPaletteFlavor[numFlavors];
    for (int i = 0; i < numFlavors; i++)
    {
      int newColor = color(0,0,0);
      String newName = "";
      switch(i)
      {
        case 0:
          newColor = color(237,238,189);
          newName = "CALCIUM";
          break;
        case 1:
          newColor = color(183,251,150);
          newName = "CARBON";
          break;
        case 2:
          newColor = color(206,244,244);
          newName = "FIRE";
          break;
        case 3:
          newColor = color(219,193,155);
          newName = "HELIUM";
          break;
        case 4:
          newColor = color(231,235,235);
          newName = "HYDROGEN";
          break;
        case 5:
          newColor = color(218,207,232);
          newName = "ICE";
          break;
        case 6:
          newColor = color(197,196,198);
          newName = "SILICON";
          break;
        case 7:
          newColor = color(250,176,71);
          newName = "SODIUM";
          break;
        case 8:
          newColor = color(250,227,71);
          newName = "SULFUR";
          break;
        case 9:
          newColor = color(208,12,7);
          newName = "TITANIUM";
          break;
      }
      int fCoords[][] = {{dims[0]+dims[2]/numFlavors*i, dims[1]},
                         {dims[0]+dims[2]/numFlavors*(i+1), dims[1]},
                         {dims[0]+dims[2]/numFlavors*(i+1), dims[1]+dims[3]},
                         {dims[0]+dims[2]/numFlavors*i, dims[1]+dims[3]}};
      flavors[i] = new tPaletteFlavor(fCoords, newColor, newName, i);
    }
  }
  
  // all of this updates the position of the fullShape
  // and the alphas of the cornerShape and fullShape
  public void update(int[] mousePos)
  {
    active = false;
    if (mousePos[0] > dims[0] && mousePos[0] < (dims[0]+dims[2]) &&
        mousePos[1] < (dims[1]+dims[3]) && mousePos[1] > dims[1])
    {
      active = true;
    }
    
    if (active)
    {
      state += 0.05f;
      state = min(state, 1.0f);
    }
    else
    {
      state -= 0.05f;
      state = max(state, 0);
    }
     
    if (state > 0.05f && state < 0.95f)
    {
      dims[1] = (int)((float)(extendedY-recessedY) * state) + recessedY;
    }
    else
    {
      if (state <= 0.05f)
      {
        dims[1] = recessedY;
      }
      else
      {
        dims[1] = extendedY;
      }
    }
    
    for (int i = 0; i < flavors.length; i++)
    {
      flavors[i].setYPosition(dims[1]);
    }
  }
  
  public boolean doDraw()
  {
    image(img.palette, dims[0], dims[1]);
    for (int i = 0; i < flavors.length; i++)
    {
      if (!flavors[i].doDraw())
      {
        return false;
      }
    }
    return true;
  }
  
  /**
   * Gets the name of the element being collided with at point pos
   * @return "" or the name of the collided element
   */
  public String getCollisionName(int[] pos)
  {
    for (int i = 0; i < flavors.length; i++)
    {
      if (flavors[i].getCollision(pos))
      {
        return flavors[i].getFlavorName();
      }
    }
    return "";
  }
  
  /**
   * Gets the color of the element being collided with at point pos
   * @return the color or 0 (so don't make any of the colors 0)
   */
  public int getCollisionColor(int[] pos)
  {
    for (int i = 0; i < flavors.length; i++)
    {
      if (flavors[i].getCollision(pos))
      {
        return flavors[i].getFlavorColor();
      }
    }
    return 0;
  }
  
  /**
   * Gets the index of the element being collided with at point pos
   * @return the index or -1
   */
  public int getCollisionFlavorIndex(int[] pos)
  {
    for (int i = 0; i < flavors.length; i++)
    {
      if (flavors[i].getCollision(pos))
      {
        return flavors[i].getFlavorIndex();
      }
    }
    return -1;
  }
}

class tPaletteFlavor
{
  tTexture myTexture;
  int myColor;
  int coords[][];
  String myName;
  int myFlavorIndex;
  
  tPaletteFlavor(int[][] newCoords, int newColor, String name, int newFlavorIndex)
  {
    coords = newCoords;
    myTexture = null;
    myColor = newColor;
    myName = name;
    myFlavorIndex = newFlavorIndex;
  }
  
  tPaletteFlavor(int[][] newCoords, int newColor, String name, int newFlavorIndex, tTexture newTexture)
  {
    coords = newCoords;
    myTexture = newTexture;
    myColor = newColor;
    myName = name;
    myFlavorIndex = newFlavorIndex;
  }
  
  public String getFlavorName()
  {
    return myName;
  }
  
  public int getFlavorColor()
  {
    return myColor;
  }
  
  public int getFlavorIndex()
  {
    return myFlavorIndex;
  }
  
  /**
   * Checks for a collision between the point pos and this flavor
   * @pos the coordinates of the point to test for
   * @return true if there is a collision, false otherwise
   */
  public boolean getCollision(int[] pos)
  {
    int coordsA[][] = {pos};
    int posA[] = {0,0};
    int coordsB[][] = coords;
    int posB[] = {0,0};
    
    return checkCollision(coordsA, coordsB, posA, posB);
  }
  
  /**
   * sets the y position of the flavor
   */
  public void setYPosition(int pos)
  {
    int h = coords[2][1] - coords[0][1];
    coords[0][1] = pos;
    coords[1][1] = pos;
    coords[2][1] = h + pos;
    coords[3][1] = h + pos;
  }
  
  /**
   * Draws the flavor
   * @return returns the result of the texture's draw
   */
  public boolean doDraw()
  {
    int pos[] = {0,0};
    if (myTexture != null)
    {
      return myTexture.doDraw(coords, pos);
    }
    
    return true;
  }
}
/**
 * The shape class
 * Shapes should be created with a set of coordinates and a color for their texture
 */
class tShape
{
  boolean visible;
  boolean hovered;
  int coords[][];
  int position[];
  tTexture textures[];
  int drawTexture;
  // used with drag'n'drop with the mouse
  // relative location on the shape
  int grabLocation[];
  // where the shape was at when the grab was started
  // so that it can "snap back" into place
  int startedGrabAt[];
  // used for generating solarSystems (see types files)
  public int elementID;
  
  /**
   * Creates a shape
   * @param coords should have at least three unique points
   * @param base_color to be used with standard_tTexture_palette()
   * @throws an exception if coords isn't correct
   */
  tShape(int[][] in_coords, int base_color) //throws Exception
  {
    int coordCount = 0;
    
    coords = in_coords;
    textures = standard_tTexture_palette(base_color);
    visible = true;
    hovered = false;
    int tempArr[][] = {{0,0},{0,0}};
    position = tempArr[0];
    startedGrabAt = tempArr[1];
    elementID = -1;
  }
  
  /**
   * updates the shape's "hovered" value based on the mouse's screen position
   * if active, then the shape will move with the mouse
   * also updates the "drawTexture" and "isActive" values
   * @param mousePos the mouse's position on the screen
   * @param active true if the shape is active (will move the shape)
   */
  public void update(int[] mousePos, boolean isActive)
  {
    // update drawTexture
    drawTexture = 0;
    if (!visible)
    {
      drawTexture = 3;
    }
    else if (isActive)
    {
      drawTexture = 2;
    }
    else if (this.getPointCollision(mousePos))
    {
      drawTexture = 1;
      hovered = true;
    }
    
    // move the shape
    if (isActive)
    {
      // update the grab location
      if (grabLocation == null)
      {
        grabLocation = new int[2];
        grabLocation[0] = mousePos[0] - position[0];
        grabLocation[1] = mousePos[1] - position[1];
        startedGrabAt[0] = position[0];
        startedGrabAt[1] = position[1];
      }
      position[0] = mousePos[0]-grabLocation[0];
      position[1] = mousePos[1]-grabLocation[1];
      position[0] -= position[0] % globalGridSize;
      position[1] -= position[1] % globalGridSize;
      int collisionColor = globalPalette.getCollisionColor(mousePos);
      if (collisionColor != 0)
      {
        this.changeColor(collisionColor);
        elementID = globalPalette.getCollisionFlavorIndex(mousePos);
        this.setAlpha(200);
      }
      else
      {
        this.setAlpha(255);
      }
    }
    else
    {
      // update the grab location
      if (grabLocation != null)
      {
        this.setAlpha(255);
        if (globalPalette.getCollisionColor(mousePos) != 0)
        {
          position[0] = startedGrabAt[0];
          position[1] = startedGrabAt[1];
        }
        else
        {
//          globalShapeContainer.snap()
        }
      }
      grabLocation = null;
    }
  }
  
  /**
   * sets the color for all textures
   * @param newColor the color to use
   */
  public void changeColor(int newColor)
  {
    for (int i = 0; i < textures.length; i++)
    {
      textures[i].changeColor(newColor);
    }
  }
  
  /**
   * sets the transparency for the color
   * should be between 0 and 255
   * @param the alpha value
   */
  public void setAlpha(int alphaVal)
  {
    for (int i = 0; i < textures.length; i++)
    {
      textures[i].setAlpha(alphaVal);
    }
  }
  
  /**
   * Draws the shape based off of drawTexture
   * @return the values returned by the texture's doDraw method
   */
  public boolean doDraw()
  {
    return textures[drawTexture].doDraw(coords, position);
  }
  
  public void setVisibility(boolean is_visible)
  {
    visible = is_visible;
  }
  
  public void setPosition(int[] pos)
  {
    position = pos;
  }
  
  /**
   * Get the bounding box of the shape, in the form:
   * [xpos relative to screen, ypos, width of bb, height]
   * @return the bounding box
   */
  public int[] getBoundingBox()
  {
    int[] retval = new int[4];
    retval[0] = coords[0][0];
    retval[1] = coords[0][1];
    retval[2] = coords[0][0];
    retval[3] = coords[0][1];
    
    for (int i = 0; i < coords.length; i++)
    {
      retval[0] = min(retval[0], coords[i][0]);
      retval[1] = min(retval[1], coords[i][1]);
      retval[2] = max(retval[2], coords[i][0]);
      retval[3] = max(retval[3], coords[i][1]);
    }
    
    retval[2] -= retval[0];
    retval[3] -= retval[1];
    
    return retval;
  }
  
  public boolean getCollision(int[][] coordsA, int[] positionA)
  {
    return checkCollision(coordsA, coords, positionA, position);
  }
  
  public boolean getPointCollision(int[] position)
  {
    int origin[] = {0,0};
    int pos[][] = {position};
    return getCollision(pos, origin);
  }
}


class tShapeContainer
{
  // shape [0] is drawn first, shape [shapes.length-1] is drawn last
  public tShape shapes[];
  // -1 when no shape is active
  int activeShape;
  
  /**
   * Creates the seven basic shapes
   */
  public void initStandardShapes()
  {
    int gs = globalGridSize;
    int boxSize = 350;
    //color baseColor = color(210,180,140);
    int baseColor = color(255,255,255);
    int midx = screenSize[0]/2-boxSize/2;
    int midy = screenSize[1]/2-boxSize/2;
    
    int coords0[][] = { {0,0}, {175,0}, {0,175} };
    int pos0[] = {0,0};
    int coords1[][] = { {175,0}, {262,87}, {174,175}, {87,88} };
    int pos1[] = {175,0};
    int coords2[][] = { {175,0}, {349,0}, {262,87} };
    int pos2[] = {175,0};
    int coords3[][] = { {87,88}, {174,175}, {87,262} };
    int pos3[] = {87,88};
    int coords4[][] = { {0,175}, {87,88}, {87,262}, {0,349} };
    int pos4[] = {0,175};
    int coords5[][] = { {0,349}, {174,175}, {349,349} };
    int pos5[] = {0,349};
    int coords6[][] = { {174,175}, {349,0}, {349,350} };
    int pos6[] = {175,174};
    
    for (int i = 0; i < 7; i++)
    {
      int coords[][] = null;
      int pos[] = null;
      switch (i)
      {
        case 0:
          coords = coords0;
          pos = pos0;
          break;
        case 1:
          coords = coords1;
          pos = pos1;
          break;
        case 2:
          coords = coords2;
          pos = pos2;
          break;
        case 3:
          coords = coords3;
          pos = pos3;
          break;
        case 4:
          coords = coords4;
          pos = pos4;
          break;
        case 5:
          coords = coords5;
          pos = pos5;
          break;
        case 6:
          coords = coords6;
          pos = pos6;
          break;
      }
      int posOffset[] = {pos[0] % gs, pos[1] % gs};
      for (int j = 0; j < coords.length; j++)
      {
        coords[j][0] += posOffset[0] - pos[0];
        coords[j][1] += posOffset[1] - pos[1];
      }
      pos[0] -= posOffset[0];
      pos[1] -= posOffset[1];
      this.addShape(coords, baseColor);
      pos[0] += midx - (midx%gs);
      pos[1] += midy - (midy%gs);
      shapes[0].setPosition(pos);
    }
  }
  
  /**
   * Adds a shape to the begining of the shapes[] array
   * @see addShape(tShape)
   * @see tShape(inCoords, baseColor)
   */
  public void addShape(int[][] coords, int baseColor)
  {
    tShape myShape = new tShape(coords, baseColor);
    this.addShape(myShape);
  }
  
  /**
   * @param newShape the shape to be added
   * @see addShape(coords, baseColor)
   */
  public void addShape(tShape newShape)
  {
    if (shapes != null && shapes.length > 0)
    {
      tShape oldShapes[] = shapes;
      shapes = new tShape[shapes.length+1];
      for (int i = 0; i < oldShapes.length; i++)
      {
        shapes[i+1] = oldShapes[i];
      }
    }
    else
    {
      shapes = new tShape[1];
    }
    shapes[0] = newShape;
  }
  
  /**
   * Draws every shape, starting with shapes[0]
   * If any shape returns false from it's draw function, stop and return false
   * @return true if all draws succeeded, false otherwise
   */
  public boolean doDraw()
  {
    for (int i = 0; i < shapes.length; i++)
    {
      boolean retval = shapes[i].doDraw();
      if (!retval)
      {
        return false;
      }
    }
    
    return true;
  }
  
  /**
   * calls the mouseUpdate function of all shapes and
   * sets the "activeShape" value
   * @param mousePos The position of the mouse
   * @see getMouseDownLeft()
   * @see isMouseDown
   */
  public void update(int mousePos[])
  {
    // find the active shape
    if (getMouseDownLeft())
    {
      if (activeShape == -1 && (frameOnWhichMouseWasLastPressed-frameCount) < 3)
      {
        for (int i = shapes.length-1; i > -1; i--)
        {
          if (shapes[i].getPointCollision(mousePos))
          {
            this.bringToEnd(i);
            activeShape = shapes.length-1;
            break;
          }
        }
      }
    }
    else
    {
      activeShape = -1;
    }
    
    // call the update function
    for (int i = 0; i < shapes.length; i++)
    {
      if (i == activeShape)
      {
        shapes[i].update(mousePos, true);
      }
      else
      {
        shapes[i].update(mousePos, false);
      }
    }
  }
  
  /**
   * Brings the shape at the given index to the end of the list,
   * shifting all shapes in front of it down
   * @shapeIndex the shape to move to the end of the list
   */
  public void bringToEnd(int shapeIndex)
  {
    if (shapes.length < 2)
    {
      return;
    }
    
    tShape myShape = shapes[shapeIndex];
    for (int i = shapeIndex; i < shapes.length-1; i++)
    {
      shapes[i] = shapes[i+1];
    }
    shapes[shapes.length-1] = myShape;
  }
  
  /**
   * Gets a colon-separated-value list of element IDs from the children shapes
   * @includeNegatives include the default -1 element ID's in the list?
   * @return a list in the form:
   *  0:-1:3:etc
   */
  public String getElementIDs(boolean includeNegatives)
  {
    String retval = "";
    boolean isFirst = true;
    for (int i = 0; i < shapes.length; i++)
    {
      int id = shapes[i].elementID;
      if (id < 0 && !includeNegatives)
      {
        continue;
      }
      if (!isFirst)
      {
        retval += ":";
      }
      retval += id;
    }
    
    return retval;
  }
}
/**
 * Draws a texture
 * Each texture has:
 *   a color, which is drawn within the bounds of the coordinates
 *   an image, which is merged with the color
 *   post_effects, which is an ArrayList of strings representing post effects to be drawn with the pf_(methodname) methods
 */
class tTexture
{
  PImage my_image;
  int my_color;
  int original_alpha;
  int original_stroke_alpha;
  ArrayList my_post_effects;
  boolean has_color;
  int my_stroke_color;
  
  tTexture()
  {
    my_image = null;
    has_color = false;
    my_post_effects = null;
  }
  
  /**
   * Call when first creating the tTexture
   * @param in_color set the properties for the tTexture
   */
  tTexture(int in_color)
  {
    my_image = null;
    my_color = in_color;
    original_alpha = (int)alpha(my_color);
    my_post_effects = null;
    has_color = true;
    set_stroke_color();
  }
  
  /**
   * Call when first creating the tTexture
   * @param in_image set the properties for the tTexture
   * @param in_color set the properties for the tTexture
   * @param in_post_effects set the properties for the tTexture
   */
  tTexture(int in_color, PImage in_image, ArrayList in_post_effects)
  {
    my_image = in_image;
    my_color = in_color;
    original_alpha = (int)alpha(my_color);
    my_post_effects = in_post_effects;
    has_color = true;
    set_stroke_color();
  }
  
  /**
   * Call to draw the texture on the specified coordinates
   * @coords a list of coordinates for a convex shape,
   *   where each coordinate is an int array[2]
   * @position the [x,y] position of the coords on the screen
   * @return the same as draw_post_effects()
   */
  public boolean doDraw(int coords[][], int position[])
  {
    draw_color_image(coords, position);
    if (my_post_effects != null)
    {
      return draw_post_effects(coords, position);
    }
    return true;
  }
  
  /**
   * Sets the color for the stroke of the texture
   * Only works if a color was set for the texture
   * defaults to half the value of the input color
   */
  public void set_stroke_color()
  {
    if (has_color)
    {
      this.set_stroke_color(color((red(my_color)+0)/2, (green(my_color)+0)/2, (blue(my_color)+0)/2));
    }
  }
  
  /**
   * Sets the color for the stroke of the texture
   * Only works if a color was set for the texture
   * @param in_stroke_color the color to set the stroke to
   */
  public void set_stroke_color(int in_stroke_color)
  {
    my_stroke_color = in_stroke_color;
    original_stroke_alpha = (int)alpha(my_stroke_color);
  }
  
  /**
   * creates the basic shape, with this tTexture's color
   * @coords a list of coordinates for a convex shape,
   *   where each coordinate is an int array[2]
   * @pos the [x,y] position of the coords on the screen
   * @in_image the image drawn with draw_color() and draw_image()
   */
  public void draw_color_image(int coords[][], int pos[])
  {
    textureMode(NORMALIZED);
    float image_vertices[][] = {{0f,0f}, {1f,0f}, {1f,1f}, {0f,1f}};
    boolean texture_works = false;
    
    beginShape();
    
    if (my_image != null)
    {
      texture(my_image);
      texture_works = true;
    }
    if (has_color)
    {
      fill(my_color);
      stroke(my_stroke_color);
    }
    
    for (int i = 0; i < coords.length; i++)
    {
      float image_vertex[] = image_vertices[min(i,image_vertices.length-1)];
      if (texture_works)
      {
        vertex(coords[i][0]+pos[0], coords[i][1]+pos[1], image_vertex[0], image_vertex[1]);
      }
      else
      {
        vertex(coords[i][0]+pos[0], coords[i][1]+pos[1]);
      }
    }
    
    endShape();
    
    line(coords[0][0]+pos[0], coords[0][1]+pos[1], coords[coords.length-1][0]+pos[0], coords[coords.length-1][1]+pos[1]);
  }
  
  /**
   * draws the post effects for the tTexture directly to the screen
   * @coords the coordinates of the shapes
   * @position the position of the 0 coordinate in coords
   * @return false if any of the post effects fail
   */
  public boolean draw_post_effects(int coords[][], int position[])
  {
    for (int pf_i = 0; pf_i < my_post_effects.size(); pf_i++)
    {
      boolean retval = true;
      String pf_type = my_post_effects.get(pf_i).toString();
      if (pf_type == "pf_highlighted_corners")
      {
        retval = pf_highlighted_corners(coords, 5, color(255,255,255));
        break;
      }
      /*else if (pf_type == "pf_something_else_here")
      {
      }*/
      if (!retval)
      {
        return false;
      }
    }
    
    return true;
  }
  
  /**
   * adds a glow effect around every coordinate
   * draws the post effect directly to the screen
   * @coords the coordinates to draw at, where each coordinate is an array [x,y]
   * @rad the radius of the glow
   * @glow_color the rgb color (alpha = 255) of the glow
   * @return true on success, false otherwise
   */
  public boolean pf_highlighted_corners(int coords[][], int rad, int glow_color)
  {
    
    return true;
  }
  
  /**
   * multiplies the input alpha by the original alpha value
   * @param alphaVal should be between 0 and 255
   */
  public void setAlpha(int alphaVal)
  {
    float a = original_alpha;
    int new_alpha = (int)(((float)alphaVal/255.0f)*a);
    my_color = color(red(my_color), green(my_color), blue(my_color), new_alpha );
    a = original_stroke_alpha;
    new_alpha = (int)(((float)alphaVal/255.0f)*a);
    my_stroke_color = color(red(my_stroke_color), green(my_stroke_color), blue(my_stroke_color), new_alpha );
  }
  
  /**
   * permanently changes the color of the texture
   * also changes the stroke color
   * @param newColor the new color
   */
  public void changeColor(int newColor)
  {
    my_color = newColor;
    set_stroke_color();
  }
}

/**
 * Returns an array of four tTextures, where the indexes correspond to:
 *   0: normal
 *   1: hovered
 *   2: active
 *   3: hidden
 * @param base_color the basic color of the texture
 * @return a tTexture[4] array
 */
public tTexture[] standard_tTexture_palette(int base_color)
{
  tTexture retval[] = {
    standard_normal_tTexture(base_color),
    standard_hovered_tTexture(base_color),
    standard_active_tTexture(base_color),
    standard_hidden_tTexture(base_color),
  };
  return retval;
}

/**
 * Returns the standard texture for a normal tangram
 * @param texture_color the base color for the texture
 * a suggested color is color(210,180,140)
 * @return a standard texture
 */
public tTexture standard_normal_tTexture(int texture_color)
{
  return new tTexture(texture_color);
}

/**
 * Returns the standard texture for a hovered tangram
 * @param texture_color the base color for the texture
 * a suggested color is color(210,180,140)
 * @return a standard texture
 */
public tTexture standard_hovered_tTexture(int texture_color)
{
  float high = max( max(red(texture_color), green(texture_color)), blue(texture_color) );
  float diff = 255 - high;
  texture_color = color(red(texture_color) + diff, green(texture_color) + diff, blue(texture_color) + diff, alpha(texture_color));
  return new tTexture(texture_color);
}

/**
 * Returns the standard texture for a active tangram
 * @param texture_color the base color for the texture
 * a suggested color is color(210,180,140)
 * @return a standard texture
 */
public tTexture standard_active_tTexture(int texture_color)
{
  texture_color = color(red(texture_color)/4*3, green(texture_color)/4*3, blue(texture_color)/4*3, alpha(texture_color));
  return new tTexture(texture_color);
}

/**
 * Returns the standard texture for a hidden tangram
 * @param texture_color the base color for the texture
 * a suggested color is color(210,180,140)
 * @return a standard texture
 */
public tTexture standard_hidden_tTexture(int texture_color)
{
  texture_color = color(red(texture_color), green(texture_color), blue(texture_color), 0);
  return new tTexture(texture_color);
}
float sunRadius = 20; //"radius" of the sun
float earthRadius = 4; //"radius" of the earth

/* Body class
* creates a Body object which represents either a star or a planet
*/
class Body{
  public float radi;
  public int ink;
  public boolean isStar;
  public boolean isPlanet;
  public int element;
  
  /*constructor
  * sets the parameters of the body and what type of body it is
  */
  Body(float rr, int c, boolean is, boolean ip, int formula) {
    this.radi = rr;
    this.ink = c;
    this.isStar = is;
    this.isPlanet = ip;
    this.element = formula;
    
    if(isStar)
      this.radi = this.radi*sunRadius;
    if(isPlanet)
      this.radi = this.radi*earthRadius;
  }
  
  /*draws the body*/
  public void display( float xpos, float ypos ){
    ellipseMode(CENTER);
    fill(this.ink);
    ellipse( xpos, ypos, this.radi, this.radi);
  }
}

/*PlanetType class
* creates an object describing a type of planet
*/
class PlanetType{
  public float planetMaximum;
  public float planetSizeMax;
  public float planetSizeMin;
  public int planetFormula;
  public int planetColor;
  
  /*constructor, pulls planet data*/
  PlanetType(float planetData[] ){
    this.planetMaximum = planetData[0];
    this.planetSizeMax = planetData[1];
    this.planetSizeMin = planetData[2];
    this.planetFormula = (int)planetData[3];
    this.planetColor = color(planetData[4],planetData[5],planetData[6]);
  }
}

/*StarType class
* creates an object describing a type of star
*/
class StarType {
   public float maxPlanets;
   public float starFormula;
   public float starSizeMax;
   public float starSizeMin;
   public int[] planetsT; 
   public int starColor;
   
   /*constructor, fills in the star data*/
   StarType(float[] starData, String pTypes){
     this.maxPlanets = starData[0];
     this.starSizeMax = starData[1];
     this.starSizeMin = starData[2];
     this.starFormula = starData[3];
     this.starColor = color(starData[4],starData[5],starData[6]);
     String[] inter = split(pTypes, ":");
     this.planetsT = PApplet.parseInt(inter);
   }
   
   public int[] getPlanets()
   {
     return this.planetsT;
   }
}

/*SystemType class
* creates an object describing a type of solar system
*/
class SystemType {
  public int rank;
  public int numStars;
  public int maxObjects;
  public int[] starsT;
  
  /*constructor, pulls system data*/
  SystemType(int[] sysData, String sTypes){
    this.rank = sysData[0];
    this.numStars = sysData[1];
    this.maxObjects = sysData[2];
    this.starsT = PApplet.parseInt(split(sTypes, ":"));
  }
}

/*SolarSystem class
* creates an object that contains all the solar system data
*/
class SolarSystem {
  public int sysType;
  public int numStars;
  public int numPlanets;
  public String fullFormula;
  public Body[] stars;
  public Body[] planets;
  public boolean isValid;
  public boolean isBlue;
  public boolean hasHabitable;
  
  /*constructor, limits the system types by the difficulty rank*/
  SolarSystem(int rank){
    if( rank > 0)
      this.sysType = (int)random(0,rank);
    else
      this.sysType = 0;
      this.isValid = true;
  }
  
  /*creates a solar system randomly*/
  public void genSystem(){
    this.isValid = true;
    this.numStars = SystemTypes[sysType].numStars;
    this.numPlanets = SystemTypes[sysType].maxObjects - numStars;
    this.stars = new Body[numStars];
    this.planets = new Body[numPlanets];
    /*check for the easy system type*/
    if(this.sysType < 1)
    {
      /*build the single star, randomly select type*/
      int starType = (int)random(0,5);
      this.stars[0] = new Body(random(StarTypes[starType].starSizeMin, StarTypes[starType].starSizeMax), StarTypes[starType].starColor, true, false, (int)StarTypes[starType].starFormula);
      this.fullFormula = ""+this.stars[0].element;
      /*generate the planets randomly*/
      for(int i = 0; i < this.numPlanets; i++)
      {
        int[] endplanet = StarTypes[starType].getPlanets();
        int planetType = (int)random(0,endplanet[endplanet.length-1]);
        while(planetType == 4 && hasHabitable)
          planetType = (int)random(0,endplanet[endplanet.length-1]);
        if(planetType == 4 && hasHabitable == false)
          this.hasHabitable = true;
        this.planets[i] = new Body(random(PlanetTypes[planetType].planetSizeMin, PlanetTypes[planetType].planetSizeMax), PlanetTypes[planetType].planetColor, false, true, PlanetTypes[planetType].planetFormula);
        this.fullFormula = this.fullFormula + ":" + this.planets[i].element;
      }
    }
  }
  
  /*build a solar system from an element list*/
  public void buildSystem(String elements){
    int starcount = 0;
    int planetcount = 0;
    int hCount = 0;
    int[] recipe = PApplet.parseInt(split(elements, ":"));
    stars = new Body[recipe.length];
    planets = new Body[recipe.length];
    for(int j = 0; j < recipe.length;j++)
    {
        if(recipe[j] == 0 || recipe[j] == 2 || recipe[j] == 4 || recipe[j] == 7 || recipe[j] == 9)
        {
          stars[starcount] = new Body(random(Elements[recipe[j]][1],Elements[recipe[j]][0]), color(Elements[recipe[j]][2],Elements[recipe[j]][3],Elements[recipe[j]][4]),true,false,recipe[j]);
          starcount++;
          if(recipe[j] == 2)
            this.isBlue = true;
        }
        else
        {
          planets[planetcount] = new Body(random(Elements[recipe[j]][1],Elements[recipe[j]][0]), color(Elements[recipe[j]][2],Elements[recipe[j]][3],Elements[recipe[j]][4]),false,true,recipe[j]);
          planetcount++;
          if(recipe[j] > 2 && isBlue)
          {
              this.isValid = false;
              println("false by blue");
          }
          if(recipe[j] == 1)
          {
            hCount++;
            this.hasHabitable = true;
          }
        }
    }
    /*check for conditions that make the system invalid*/
    /*more than a trinary system*/
    if(starcount > 3)
      this.isValid = false;
    /*too many stars for a habitable planet*/
    if(starcount > 1 && hasHabitable)
      this.isValid = false;
    /*too many habitable planets*/
    if(hCount > 1)
      this.isValid = false;
    if(!isValid)
      println("not valid by #stars or #habitable");
      
    numStars = starcount;
    numPlanets = planetcount;
      
    if(isValid)
      this.displaySystem();
    else
      this.explode();
    
  }
  
  /*build the solar system on the screen*/
  public void displaySystem(){
    float startx = -this.stars[0].radi;
    float starty = height/2;
    int x = 30;
    for(int k = 0; k < this.numStars; k++)
    {
      startx = startx + this.stars[k].radi;
      this.stars[k].display(startx, starty);
      println("startx = " + startx);
      startx = startx + this.stars[k].radi;
    }
    
    for(int k = 0; k < this.numPlanets; k++)
    {
      startx = startx + this.planets[k].radi;
      this.planets[k].display(startx, starty);
      println("startx = " + startx);
      startx = startx + this.planets[k].radi;
    }

    fill(155);
    text("Not to scale.", x, 700);    
  }
  
  /*create the explosion that indicates a failed system*/
  public void explode() {
    background(0);
    stroke(255);
    strokeWeight(10);

    for( int i = 0; i < NUM ;i++) {
      point( exppos[i].x + width/2, exppos[i].y + height );
    }

    for( int i = 0; i < NUM; i++) {
      exppos[i].add(expvel[i]);
    }
  }
  
}

  static public void main(String args[]) {
    PApplet.main(new String[] { "--bgcolor=#F0F0F0", "spaceminers_sketch" });
  }
}
